package l.com.qqdemo;

import android.animation.FloatEvaluator;
import android.animation.IntEvaluator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

import com.nineoldandroids.view.ViewHelper;

/**
 * 菜单控件
 */

public class SlidMenu extends FrameLayout {
    private View menuView;//菜单的view
    private View mainView;//主界面的view
    // FIXME: 2017/12/25 viewDragHelper使用步骤  step 1
    private ViewDragHelper viewDragHelper;
    private int width;
    private float dragRange;//拖拽范围
    private DragCallback callback;
    private FloatEvaluator floatEvaluator;
    private IntEvaluator intEvaluator;
    private OnDragStateChangeListener listener;
    private DragState currentState = DragState.Close;//当前SlideMenu的状态默认是关闭的

    public SlidMenu(@NonNull Context context) {
        super(context);
        init();
    }


    public SlidMenu(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public SlidMenu(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public DragState getCurrentState() {
        return currentState;
    }

    //定义状态常量
    enum DragState {
        Open, Close
    }

    private void init() {
        // FIXME: 2017/12/25 step 2 初始化viewDragHelper
        callback = new DragCallback();
        floatEvaluator = new FloatEvaluator();
        intEvaluator = new IntEvaluator();
        viewDragHelper = ViewDragHelper.create(this, callback);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        //简单的异常处理
        if (getChildCount() != 2) {
            // FIXME: 2017/12/25 异常的写法和应用
            throw new IllegalArgumentException("SlideMenu only have 2 children!");
        }
        menuView = getChildAt(0);
        mainView = getChildAt(1);
    }

    /**
     * fixme 该方法在onMeasure执行完之后执行，那么可以在该方法中初始化自己和子View的宽高
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = getMeasuredWidth();
        dragRange = width * 0.6f;  // TODO: 2017/12/25 主界面拖拽范围,屏幕宽度的0.6
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // FIXME: 2017/12/25 step 4 传递给touchEvent
        viewDragHelper.processTouchEvent(event);
        return true;   // FIXME: 2017/12/25 step 5 return true
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // FIXME: 2017/12/25 step 6 传递给拦截事件
        return viewDragHelper.shouldInterceptTouchEvent(ev);
    }

    // FIXME: 2017/12/25 step 3 初始化callback
    class DragCallback extends ViewDragHelper.Callback {
        /**
         * todo tryCaptureView  捕捉view
         */
        @Override
        // TODO: 2017/12/25 事件处理 step 1 捕捉view
        public boolean tryCaptureView(View child, int pointerId) {
            return child == menuView || child == mainView;
        }

        /**
         * 获取view水平方向的拖拽范围,但是目前不能限制边界,返回的值目前用在手指抬起的时候view缓慢移动的动画世界的计算上面;
         * 最好不要返回0
         */
        // TODO:  拖拽范围
        public int getViewHorizontalDragRange(View child) {
            // TODO: 2017/12/25 获取view水平方向的拖拽范围,但是目前不能限制边界
            return (int) dragRange;
        }

        /**
         * 控制child在水平方向的移动 left:
         * 表示ViewDragHelper认为你想让当前child的left改变的值,left=chile.getLeft()+dx dx:
         * 本次child水平方向移动的距离 return: 表示你真正想让child的left变成的值
         */
        //todo clampViewPositionHorizontal 移动边界确定
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (child == mainView) {
                if (left < 0)
                    left = 0;//限制mainView的左边
                if (left > dragRange)
                    left = (int) dragRange;//限制mainView的右边
            }/* else if (child == menuView) {
                left = left-dx;//fixme  菜单固定不动,但这样dx=0,不合要求
            }
*/
            return left;
        }


        @Override
        // todo onViewPositionChanged  做伴随移动 view.layout()  和 滑动百分比 fraction的监听
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            if (changedView == menuView) {

                //fixme  其实是可以在 clampViewPositionHorizontal中固定菜单的,
                // fixme 但是这样的话,dx的值是0,主界面无法做伴随移动,所以用layout的方式限制菜单移动


                //todo 固定住menuView  向左拖动menuView让mainView动起来使用的是伴随动画实现的
                menuView.layout(0, 0, menuView.getMeasuredWidth(), menuView.getMeasuredHeight());
                //todo  让mainView移动起来
                int newLeft = mainView.getLeft() + dx;
                // TODO: 2017/12/25 限制mainView边界
                if (newLeft < 0)
                    newLeft = 0;//限制mainView的左边
                if (newLeft > dragRange)
                    newLeft = (int) dragRange;//限制mainView的右边
                mainView.layout(newLeft, mainView.getTop() + dy, newLeft + mainView.getMeasuredWidth(), mainView.getBottom() + dy);
            }
            // TODO: 2017/12/25 伴随动画,状态更改及接口回调
            //1.计算滑动的百分比
            float fraction = mainView.getLeft() / dragRange;
            //2.执行伴随动画
            executeAnim(fraction);
            //3.更改状态，回调listener的方法
            // TODO:  fraction==0 表示要更改当前状态为关闭,  currentState!=DragState.Close 防止拖动来回又一次调用回调方法
            if (fraction == 0 && currentState != DragState.Close) {
                //更改状态为关闭，并回调关闭的方法
                currentState = DragState.Close;
                if (listener != null)
                    listener.onClose();
            }
            // TODO: 2017/12/25 判断条件同上
            else if (fraction == 1f && currentState != DragState.Open) {
                //更改状态为打开，并回调打开的方法
                currentState = DragState.Open;
                if (listener != null)
                    listener.onOpen();
            }
            //将drag的fraction暴漏给外界
            if (listener != null) {
                listener.onDraging(fraction);
            }

        }

        /**
         * 当child的位置改变的时候执行,一般用来做其他子View的伴随移动 changedView：位置改变的child
         * left：child当前最新的left top: child当前最新的top dx: 本次水平移动的距离 dy: 本次垂直移动的距离
         */
        @Override
        //todo onViewReleased 释放,平滑滚动
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            if (mainView.getLeft() < dragRange / 2) {
                //在左半边
                close();
            } else {
                //在右半边
                open();
            }
            //fixme xvel是速度 处理用户的稍微滑动,用户稍微滑动就能打开或关闭菜单,不用判断到菜单的1/2才有效果
            if (xvel > 200 && currentState != DragState.Open) {
                open();
            } else if (xvel < -200 && currentState != DragState.Close) {
                close();
            }
        }
    }

    /**
     * 打开菜单
     */
    public void close() {
        viewDragHelper.smoothSlideViewTo(mainView, 0, mainView.getTop());
        ViewCompat.postInvalidateOnAnimation(SlidMenu.this);
    }

    /**
     * 打开菜单
     */
    public void open() {
        viewDragHelper.smoothSlideViewTo(mainView, (int) dragRange, mainView.getTop());
        ViewCompat.postInvalidateOnAnimation(SlidMenu.this);
    }

    // fixme: 2017/12/25  不要忘了,没有这个方法,是不会伴随滚动的
    public void computeScroll() {
        if (viewDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(SlidMenu.this);
        }
    }


    /**
     * 执行伴随动画
     *
     * @param fraction
     */
    private void executeAnim(float fraction) {
        //fraction:0-1

        //		float scaleValue = 0.8f+0.2f*(1-fraction);//1-0.8f
        // TODO: 2017/12/25 计算器  变化率  起始值  最终值
        /*
         public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
        */
        // TODO: 2017/12/25 伴随动画步骤
        //todo step 1  缩小mainView
        ViewHelper.setScaleX(mainView, floatEvaluator.evaluate(fraction, 1f, 0.8f));
        ViewHelper.setScaleY(mainView, floatEvaluator.evaluate(fraction, 1f, 0.8f));
        //todo step 2 移动menuView
        ViewHelper.setTranslationX(menuView, intEvaluator.evaluate(fraction, -menuView.getMeasuredWidth() / 2, 0));
        //todo step 3 放大menuView
        ViewHelper.setScaleX(menuView, floatEvaluator.evaluate(fraction, 0.5f, 1f));
        ViewHelper.setScaleY(menuView, floatEvaluator.evaluate(fraction, 0.5f, 1f));
        //todo step 4 改变menuView的透明度
        ViewHelper.setAlpha(menuView, floatEvaluator.evaluate(fraction, 0.3f, 1f));
        //todo step 5 给SlideMenu的背景添加黑色的遮罩效果   背景的颜色变化效果
        //fixme ColorUtil参照argb计算器  ArgbEvaluator   Drawable.setColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode)
        getBackground().setColorFilter((Integer) ColorUtil.evaluateColor(fraction, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER);

    }


    public void setOnDragStateChangeListener(OnDragStateChangeListener listener) {
        this.listener = listener;
    }

    /**
     * todo 主界面小人透明和晃动的动画是具体view中的动画,menu控制着两层,并不能具体指定,所以这里需要接口暴露比率和开关控制外部调用
     */
    public interface OnDragStateChangeListener {
        /**
         * 打开的回调
         */
        void onOpen();

        /**
         * 关闭的回调
         */
        void onClose();

        /**
         * 正在拖拽中的回调
         */
        void onDraging(float fraction);
    }
}
